/*
Copyright 2010  Murali Devi (profdevi@gmail.com)

This file is part of ComineGL Project.

ComineGL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ComineGL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ComineGL.  If not, see <http://www.gnu.org/licenses/>.

This code uses the irrlicht and irrklang libraries.  Please read
the libraries copyrights at 

	irrlicht library:  http://irrlicht.sourceforge.net
	irrklang library:  http://www.ambiera.com/irrklang

*/



//v1.4 copyright Comine.com 20101121U04
#include "MStdLib.h"
#include "MIntList.h"
#include "MPtrList.h"
#include "MTimeEventQueue.h"


//*****************************************************
//** Module Events
//*****************************************************
static const int GBadEventID=(1<<(sizeof(int)*8-1));


//*****************************************************
//** Module Events
//*****************************************************
struct GNode
	{
	unsigned int Time;										// Time when event triggers
	unsigned int DeltaTime;									// >0 => Repeat in Time+DeltaTime
	bool (*Action)(class MTimeEventQueue &manager,int eventid,void *extra);		// Trigger Action
	bool (*OnEnd)(class MTimeEventQueue &manager,int eventid,void *extra);		// OnEnd of Trigger
	int EventID;											// Event ID
	void *Extra;											// Extra Data
	GNode *Next;											// Next TimeEvent
	GNode *Last;											// Last TimeEvent
	};


//******************************************************
//**  MTimeEventQueue class
//******************************************************
void MTimeEventQueue::ClearObject(void)
	{
	mClockTime=0;
	mEventCount=0;
	mEventList=NULL;
	mRemovedCount=0;
	}

////////////////////////////////////////////////
struct GNode *MTimeEventQueue::FindEvent(int eventid)
	{
	GNode *p;
	for(p=mEventList->Next;p!=NULL;p=p->Next)
		{
		if(p->EventID==eventid)
			{
			return p;
			}
		}

	return NULL;
	}


////////////////////////////////////////////////
bool MTimeEventQueue::UpdateClockTime(unsigned clocktime)
	{
	if(clocktime>=mClockTime)
		{
		mClockTime=clocktime;
		return true;
		}

	//=We have to update the clock time and all events
	unsigned int difftime=mClockTime-clocktime;
	
	GNode *p;
	for(p=mEventList->Next;p!=NULL;p=p->Next)
		{
		p->Time = clocktime + p->Time - mClockTime;
		}

	// Now Update Clocktime
	mClockTime=clocktime;

	return true;
	}


////////////////////////////////////////////////
bool MTimeEventQueue::LinkNode(GNode *pevent)
	{
	MStdAssert(pevent!=NULL);

	if(mEventList->Next==NULL)
		{
		pevent->Last=mEventList;
		mEventList->Next=pevent;

		mEventCount=mEventCount+1;
		return true;
		}

	GNode *p;
	for(p=mEventList->Next;  ;p=p->Next)
		{
		if(p->Time>pevent->Time)
			{
			// Insert before this node
			pevent->Last=p->Last;
			pevent->Next=p;
			pevent->Last->Next=pevent;
			p->Last=pevent;

			mEventCount=mEventCount+1;
			return true;
			}
		
		// We have reached the end of the list
		if(p->Next==NULL)
			{
			// Add to end
			p->Next=pevent;
			pevent->Last=p;
			mEventCount = mEventCount + 1;
			return true;
			}
		}

	return true;
	}


///////////////////////////////////////////////
bool MTimeEventQueue::UnlinkNode(GNode *pevent)
	{
	MStdAssert(pevent!=NULL);

	if(pevent->Last!=NULL)
		{
		pevent->Last->Next = pevent->Next;
		}

	if(pevent->Next!=NULL)
		{
		pevent->Next->Last = pevent->Last;
		}

	pevent->Last=NULL;
	pevent->Next=NULL;

	mEventCount=mEventCount-1;
	return true;
	}


////////////////////////////////////////////////
bool MTimeEventQueue::CallOnEnd(GNode *pevent)
	{
	MStdAssert(pevent!=NULL);

	if(pevent->OnEnd!=NULL && pevent->OnEnd(*this,pevent->EventID,pevent->Extra)==false)
		{
		//=continue anyway
		}

	pevent->OnEnd=NULL;
	return true;
	}


////////////////////////////////////////////////
bool MTimeEventQueue::DeleteNode(GNode *pevent)
	{
	CallOnEnd(pevent);

	// Release memory
	delete pevent;
	return true;
	}


////////////////////////////////////////////////
MTimeEventQueue::MTimeEventQueue(void)
	{  ClearObject();  }


////////////////////////////////////////////////
MTimeEventQueue::~MTimeEventQueue(void)
	{  Destroy();  }


////////////////////////////////////////////////
bool MTimeEventQueue::Create(unsigned int clocktime)
	{
	Destroy();

	mEventList=new GNode;
	if(mEventList==NULL)
		{
		Destroy();
		return false;
		}

	// Init Dummy Node
	mEventList->Time=0;
	mEventList->DeltaTime=0;
	mEventList->Action=NULL;
	mEventList->OnEnd=NULL;
	mEventList->EventID=0;
	mEventList->Extra=NULL;
	mEventList->Last=NULL;
	mEventList->Next=NULL;	

	// Set Clock Time
	mClockTime=clocktime;
	
	return true;
	}


////////////////////////////////////////////////
bool MTimeEventQueue::Destroy(void)
	{
	if(mEventList!=NULL)
		{
		GNode *p;
		GNode *ptmp;

		for(p=mEventList;p!=NULL;p=ptmp)
			{
			ptmp=p->Next;			
			CallOnEnd(p);
			DeleteNode(p);
			}

		mEventList=NULL;
		}

	ClearObject();
	return true;
	}


///////////////////////////////////////////////
bool MTimeEventQueue::EventAdd(int eventid,
		bool repeat,
		unsigned int deltatime,
		bool action(class MTimeEventQueue &manager,int eventid,void *extra),
		bool onend(class MTimeEventQueue &manager,int eventid,void *extra),
		void *extra)
	{
	if(deltatime==0 || action==NULL )
		{
		return false;
		}
	
	// Search for event
	GNode *pevent=FindEvent(eventid);
	if(pevent!=NULL)
		{
		UnlinkNode(pevent);
		CallOnEnd(pevent);
		}
	else
		{
		// Allocate space for event
		pevent=new GNode;
		if(pevent==NULL)
			{
			return false;
			}
		}

	//**Init event node		
	pevent->Time = mClockTime+deltatime;

	if(repeat==true)
		{
		pevent->DeltaTime=deltatime;
		}
	else
		{
		pevent->DeltaTime=0;
		}

	pevent->EventID=eventid;
	pevent->Action=action;
	pevent->OnEnd=onend;
	pevent->Extra=extra;
	pevent->Last=NULL;
	pevent->Next=NULL;

	//** Now link event into list
	LinkNode(pevent);
	return true;
	}


////////////////////////////////////////////
bool MTimeEventQueue::EventCancel(int eventid)
	{
	GNode *pevent=FindEvent(eventid);
	if(pevent==NULL)
		{
		return false;
		}

	// Set Action to NULL and mark count of nodes to be erased
	// after processing all the events

	pevent->Action = NULL;

	// Perform cleanup of event
	CallOnEnd(pevent);

	// Set Event to be bad
	pevent->EventID = GBadEventID;
	mRemovedCount = mRemovedCount + 1;

	return true;
	}


///////////////////////////////////////////////
bool MTimeEventQueue::EventCancelAll(void)
	{
	GNode *pevent;
	GNode *nextnode;
	for(pevent=mEventList->Next;pevent!=NULL;pevent=nextnode)
		{
		nextnode=pevent->Next;
		UnlinkNode(pevent);
		pevent->Action=NULL;
		CallOnEnd(pevent);
		DeleteNode(pevent);
		}
	
	return true;
	}


///////////////////////////////////////////////
int MTimeEventQueue::GetEventCount(void)
	{
	return mEventCount;
	}


///////////////////////////////////////////////
bool MTimeEventQueue::ProcessEvents(unsigned int clocktime)
	{
	UpdateClockTime(clocktime);

	GNode *p;
	GNode *pnext;
	for(p=mEventList->Next;p!=NULL;p=pnext)
		{
		pnext=p->Next;
		if(p->Time>mClockTime)
			{
			break;
			}

		if(p->Action!=NULL)
			{
			if(p->Action(*this,p->EventID,p->Extra)==false)
				{
				p->DeltaTime=0;
				}
			}

		UnlinkNode(p);

		if(p->DeltaTime==0)
			{
			DeleteNode(p);
			}
		else
			{
			// Update the time
			p->Time=p->Time+p->DeltaTime;
			LinkNode(p);
			}
		}

	// Now We clean up all 
	if(mRemovedCount<=0)
		{
		return true;
		}
	else
		{
		mRemovedCount=0;
		}

	// Go through list removing all removed events
	for(p=mEventList->Next;p!=NULL;p=pnext)
		{
		pnext=p->Next;

		if(p->Action!=NULL)
			{
			continue;
			}

		UnlinkNode(p);
		DeleteNode(p);		
		}

	return true;
	}


/////////////////////////////////////////////////////
unsigned int MTimeEventQueue::GetClockTime(void)
	{
	return mClockTime;
	}


////////////////////////////////////////////////////
bool MTimeEventQueue::GetEvents(MIntList &list)
	{
	if(list.Create()==false)
		{
		return false;
		}

	GNode *p;
	for(p=mEventList->Next;p!=NULL;p=p->Next)
		{
		if(list.Add(p->EventID)==false)
			{
			return false;
			}
		}

	return true;
	}


////////////////////////////////////////////////////
bool MTimeEventQueue::GetEventsExtra(MPtrList &list)
	{
	if(list.Create()==false)
		{
		return false;
		}

	GNode *p;
	for(p=mEventList->Next;p!=NULL;p=p->Next)
		{
		if(list.Add(p->Extra)==false)
			{
			return false;
			}
		}

	return true;
	}

